iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Web 3

從 區塊鏈 到 去中心化應用程式(DApp)系列 第 14 篇

區塊鏈建立: 區塊運算 之 交易的流向

  • 分享至 

  • xImage
  •  

區塊運算 之 交易的流向

專案 GitHub 位置: https://github.com/weiawesome/block-chain_go

基本上開始進入 對於區塊中計算的環節

首先要面對的便是 交易的處理

交易從進到節點 到交易進去區塊計算的細節為何
這便是今日實現的重點

因此此篇根據以下幾點進行敘述

  1. 交易的驗證
  2. 從交易池中 選擇並構成區塊

完成的範圍便是 接受驗證交易 與 刷新區塊

接受驗證交易

Channel 名稱 Channel 類型 地點
TransactionChannel 進 廣播系統
BroadcastTransactionChannel 出 廣播系統
BlockTransactionChannel 出 刷新區塊

刷新區塊

Channel 名稱 Channel 類型 地點
BlockTransactionChannel 進 接受驗證交易
CompleteBlockChannel 進 刷新資料庫
MinersBlockChannel 出 礦工管理者

交易的驗證

檔案位置: "./service/receive_validate_transaction/receive_validate_transaction.go"

func ReceiveValidateTransaction(TransactionChannel chan transaction.Transaction, BroadcastTransactionChannel chan transaction.Transaction, BlockTransactionChannel chan blockchain.BlockTransaction) {
	for {
		select {
		case t := <-TransactionChannel:
			block, err := block_control.GetLastBlock()
			if err != nil {
				continue
			}
			if blockdb.TransactionInCheckedBlocks(block, t.TransactionHash) {
				StringValue, err := t.ToString()
				if err != nil {
					continue
				}
				key, err := utils.DecodePublicKey(t.PublicKey)
				if err != nil {
					continue
				}
				address := utils.GetAddress(conseous.VersionPrefix, key)
				if utils.Verify(t.Signature, key, StringValue) {
					sumOfHave := float64(0)
					flag := false
					for _, from := range t.From {
						value, err := utxo.GetUTXO(from.UTXOHash, from.Index)
						if err != nil || value.Spent == true {
							continue
						}
						if address != value.Address {
							flag = true
							break
						}
						sumOfHave += value.Amount
					}
					if flag {
						continue
					}
					sumOfSpent := float64(0)
					for _, to := range t.To {
						sumOfSpent += to.Amount
					}
					sumOfSpent += t.Fee

					if sumOfSpent > sumOfHave {
						continue
					}
					BroadcastTransactionChannel <- t
					BlockTransactionChannel <- transactionUtils.ConvertTransaction(t)
				}
			}
		default:
			continue
		}
	}
}
  1. 首先 先獲得最後區塊哈希值
  2. 確認附近區塊 有無相同交易(避免雙重花費
  3. 獲取公鑰 進行簽名確認
  4. 進行確認 擁有的金額是否大於等於手續費加支出金額
  5. 上述都沒問題 廣播出去 與 交給區塊生成

至於公私鑰相關 簽名驗證方法
檔案位置: "./utils/ecc.go"

type SignatureData struct {
	R *big.Int `json:"r"`
	S *big.Int `json:"s"`
}

type KeyPair struct {
	PublicKey  string `json:"public_key"`
	PrivateKey string `json:"private_key"`
}

func GenerateKeyPair() (KeyPair, error) {
	curve := elliptic.P256()
	privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		return KeyPair{}, err
	}
	privateKeyBytes := privateKey.D.Bytes()
	privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKeyBytes)

	publicKey := &privateKey.PublicKey
	publicKeyBytes := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
	publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKeyBytes)

	return KeyPair{PublicKey: publicKeyBase64, PrivateKey: privateKeyBase64}, err
}

func DecodePublicKey(PublicKey string) (*ecdsa.PublicKey, error) {
	curve := elliptic.P256()

	decodedPublicKeyBytes, err := base64.StdEncoding.DecodeString(PublicKey)
	if err != nil {
		return &ecdsa.PublicKey{}, err
	}

	decodedPublicKey := new(ecdsa.PublicKey)
	decodedPublicKey.Curve = curve
	decodedPublicKey.X, decodedPublicKey.Y = elliptic.Unmarshal(curve, decodedPublicKeyBytes)

	return decodedPublicKey, nil
}

func DecodePrivateKey(PrivateKey string) (*ecdsa.PrivateKey, error) {
	curve := elliptic.P256()
	decodedPrivateKeyBytes, err := base64.StdEncoding.DecodeString(PrivateKey)
	if err != nil {
		return &ecdsa.PrivateKey{}, err
	}

	decodedPrivateKey := new(ecdsa.PrivateKey)
	decodedPrivateKey.Curve = curve
	decodedPrivateKey.D = new(big.Int).SetBytes(decodedPrivateKeyBytes)

	return decodedPrivateKey, nil
}

func Signature(Content string, PrivateKey *ecdsa.PrivateKey) (string, error) {
	ContentBytes := []byte(Content)
	r, s, err := ecdsa.Sign(rand.Reader, PrivateKey, ContentBytes)
	if err != nil {
		return "", err
	}
	jsonData, err := json.Marshal(SignatureData{R: r, S: s})
	if err != nil {
		return "", err
	}
	return string(jsonData), nil
}

func Verify(Signature string, PublicKey *ecdsa.PublicKey, Content string) bool {
	ContentBytes := []byte(Content)
	var signature SignatureData

	err := json.Unmarshal([]byte(Signature), &signature)
	if err != nil {
		return false
	}

	valid := ecdsa.Verify(PublicKey, ContentBytes, signature.R, signature.S)

	return valid
}

包含生成、解碼、簽名、驗證 各種方法都有

從交易池中 選擇並構成區塊

首先 何為交易池

基本上 就是一個優先佇列(Priority Queue)
當一筆交易的手續費越高
則處理的優先順序越高

檔案位置: "./utils/priority_queue.go"

type PriorityQueue []*blockchain.BlockTransaction

func (pq *PriorityQueue) Len() int { return len(*pq) }

func (pq *PriorityQueue) Less(i, j int) bool {
	return (*pq)[i].Fee < (*pq)[j].Fee
}

func (pq *PriorityQueue) Swap(i, j int) {
	(*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i]
}

func (pq *PriorityQueue) Push(x interface{}) {
	item := x.(*blockchain.BlockTransaction)
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	*pq = old[0 : n-1]
	return item
}

如此一來 當確認過的交易 先丟進優先佇列當中
並逐漸挑選出 手續費較高的交易

檔案位置: "./protocal/refresh_block/refresh_block.go"

func RefreshBlock(BlockTransactionChannel chan blockchain.BlockTransaction, MinersBlockChannel chan blockchain.Block, CompleteBlockChannel chan blockchain.Block) {
	TransactionPool := make(utils.PriorityQueue, 0)
	var TargetBlock blockchain.Block
	key, err := utils.DecodePublicKey(utils.GetPublicKey())
	if err != nil {
		return
	}
	address := utils.GetAddress(conseous.VersionPrefix, key)
	minerTransaction, err := transactionUtils.MinerTransaction(address)
	if err != nil {
		return
	}
	for {
		select {
		case bt := <-BlockTransactionChannel:
			heap.Push(&TransactionPool, bt)
			var tmpBlock blockchain.Block
			tmpBlock.BlockTransactions = append(tmpBlock.BlockTransactions, minerTransaction)
			tmpTransactionPool := TransactionPool
			for tmpTransactionPool.Len() > 0 {
				t := heap.Pop(&tmpTransactionPool).(*blockchain.BlockTransaction)
				tmpBlock.BlockTransactions = append(tmpBlock.BlockTransactions, *t)
				if unsafe.Sizeof(tmpBlock) > conseous.BlockSize {
					tmpBlock.BlockTransactions = tmpBlock.BlockTransactions[:len(tmpBlock.BlockTransactions)-1]
					break
				}
			}
			tmpBlock.ComputeMarkleRoot()
			if tmpBlock.BlockTop.MarkleRoot != TargetBlock.BlockTop.MarkleRoot {
				TargetBlock = tmpBlock
				TargetBlock.BlockTop.Version = conseous.Version
				TargetBlock.BlockTop.TimeStamp = time.Now().Unix()
				blockHash, err := block_control.GetLastBlock()
				if err != nil {
					blockHash = conseous.GenesisBlockPreviousHash
					TargetBlock.BlockMeta = blockchain.BlockMeta{Content: conseous.GenesisBlockContent}
					TargetBlock.BlockTop.BlockHeight = conseous.GenesisBlockHeight
					TargetBlock.BlockTop.Difficulty = conseous.GenesisBlockDifficulty
				} else {
					b, err := block.GetBlock(blockHash)
					if err != nil {
						continue
					}
					TargetBlock.BlockTop.BlockHeight = b.BlockTop.BlockHeight + 1
					TargetBlock.BlockTop.Difficulty = b.BlockTop.Difficulty
					if TargetBlock.BlockTop.BlockHeight%conseous.DifficultyCycle == 0 {
						speed, err := block.CheckGenerateSpeed(TargetBlock.BlockTop.BlockHeight)
						if err == nil {
							if speed {
								if TargetBlock.BlockTop.Difficulty > conseous.DifficultyLower {
									TargetBlock.BlockTop.Difficulty -= 1
								}
							} else {
								if TargetBlock.BlockTop.Difficulty < conseous.DifficultyUpper {
									TargetBlock.BlockTop.Difficulty += 1
								}
							}
						}
					}
				}
				TargetBlock.BlockTop.PreviousHash = blockHash
				MinersBlockChannel <- TargetBlock
			}
		case cb := <-CompleteBlockChannel:
			continue
		default:
			continue
		}
	}
}

目前CompleteBlockChannel部分內容先省略

  • 程序開始時 先計算節點的 礦工交易
  1. 當新確認交易出現時
  2. 將交易加入交易池當中
  3. 持續添加至區塊當中 直到沒有交易 或是 區塊容量過大
  4. 確認區塊是否變化 有變化則填充其他資訊
  5. 最後將新區塊 傳給 礦工管理者

計算礦工交易時必須先知道 礦工的地址
而礦工的地址與公鑰息息相關

檔案位置: "./utils/address.go"

func GetAddress(networkPrefix byte, publicKey *ecdsa.PublicKey) string {
	publicKeyHash := publicKeyHash(publicKey)
	payload := append([]byte{networkPrefix}, publicKeyHash...)
	checksum := checksum(payload)
	payload = append(payload, checksum...)
	address := base58Encode(payload)
	return address
}

func publicKeyHash(publicKey *ecdsa.PublicKey) []byte {
	pubKeyBytes := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
	sha256Hash := sha256.Sum256(pubKeyBytes)
	h := ripemd160.New()
	h.Write(sha256Hash[:])
	publicKeyHash := h.Sum(nil)
	return publicKeyHash
}

func checksum(payload []byte) []byte {
	firstSHA := sha256.Sum256(payload)
	secondSHA := sha256.Sum256(firstSHA[:])
	return secondSHA[:4]
}

func base58Encode(input []byte) string {
	base58Alphabet := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

	var result []byte

	x := new(big.Int).SetBytes(input)
	base := big.NewInt(58)
	zero := big.NewInt(0)

	for x.Cmp(zero) > 0 {
		mod := new(big.Int)
		x.DivMod(x, base, mod)
		result = append([]byte{base58Alphabet[mod.Int64()]}, result...)
	}

	for _, b := range input {
		if b == 0x00 {
			result = append([]byte{base58Alphabet[0]}, result...)
		} else {
			break
		}
	}

	return string(result)
}

公鑰轉換地址步驟

  1. 首先將公鑰內容哈希
  2. 加入基本資訊
  3. 確認內容 與 添加確認資訊
  4. 重新進行編碼

當中在設定時區塊難度時 可以根據平均區塊計算時間去變化

那基本上也屬於區塊鏈協定的一環 不同的區塊鏈規定都不同
在此處可以將相關設定參數 都設定好

檔案位置: "./protocal/conseous/parameters.go"

const (
	BlockChecked             = 6
	DifficultyCycle          = 120
	AverageBlockGenerateTime = 60 * 30
	Version                  = 1
	BlockSize                = 10 * 1024 * 1024
	GenesisBlockPreviousHash = ""
	GenesisBlockContent      = "This is the block-chain made by Tcweeei!"
	GenesisBlockHeight       = 0
	GenesisBlockDifficulty   = 1
	VersionPrefix            = 0x00
	DifficultyLower          = 0
	DifficultyUpper          = 256
	MineEmpty                = false
)

包含各種基本設定的資訊

結言

基本上 對於交易進來的順序應該很清晰了

交易會遇到甚麼關卡檢驗 甚麼確認

而當中交易池的概念也很重要
(畢竟工作效率還是高一點好、拿工資(手續費)高的交易做)

希望透過這篇能夠理解

  1. 交易經過節點的流程
  2. 交易被檢驗需經過的關卡
  3. 交易池的概念
  4. 區塊計算 與 區塊難度變化

下回預告

馬不停蹄的繼續前進~~~

衝阿!!! 處理完交易 下個步驟當然是 區塊

下回 "區塊鏈建立: 區塊運算 之 區塊的流向"


上一篇
區塊鏈建立: 連線協定 與 初始化設定
下一篇
區塊鏈建立: 區塊運算 之 區塊的流向
系列文
從 區塊鏈 到 去中心化應用程式(DApp) 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言